/*
    Borealis Library
    A library of commonly used hydrological modeling building blocks

    Copyright (C) 2008  Chris Marsh

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#ifndef ARCASCIIFILE_H
#define ARCASCIIFILE_H

#define BOOST_SPIRIT_THREADSAFE

#include <string>
#include <fstream>

#include <boost/lexical_cast.hpp>


#pragma warning(disable: 4244) //disable the warning in threaded_parse() w.r.t the cast from double to float
 #define BOOST_SPIRIT_THREADSAFE
#include <boost/spirit/include/classic.hpp>


#include <tbb/blocked_range2d.h>
#include <tbb/parallel_for.h>

#include "RegexTokenizer.hpp"
#include "Raster.hpp"

namespace Borealis
{

	class AsciiRaster : public Raster<float>
	{

	public:
	
		/*
		Function: OpenFromFile
			Opens an 2 dimension x,y ascii raster ASCII overwriting any current data.
			Data can be either integer (+/-) or single precision floating point (+/-). The file must have the following header with no leading blanks lines:
			> ncols         <int>
			> nrows         <int>
			> xllcorner     <int>
			> yllcorner     <int>
			> cellsize      <int>
			> NODATA_value  <float/int>
			Values are tab/space delimited. 
			
		Parameters:
			std::string path - Path to the file to open
		
		Throws:
			std::runtime_error if the file is not a valid ascii raster
			std::bad_alloc if not enough memory can be allocated to hold the array

		Returns:
			void
		*/
		void OpenFromFile(std::string path);


		/*
			Function: AsciiRaster
				Copy Constructor
			
			Parameters: 
				AsciiRaster & in - Raster to copy from
			
			Throws:
				Never
		
			Returns:   
				 
		*/
		AsciiRaster(AsciiRaster& in);

		/*
			Function: AsciiRaster
				Constructor for an empty raster
			
			Parameters: 
				None

			Throws:
				Never
		
			Returns:   
				 
		*/
		AsciiRaster();

		/*
			Function: AsciiRaster
				Creates a new raster row x col
			
			Parameters: 
				unsigned int row - Rows
				unsigned int col - Columns
			
			Throws:
				std::bad_alloc if the memory can't be allocated
		
			Returns:   
				 -
		*/
		AsciiRaster(unsigned int row, unsigned int col);

		~AsciiRaster();

		/*
		Function: SaveToFile
			Saves the current raster in memory to a file

		Parameters: 
			std::string outFile - Path to the file

		Throws:
			std::runtime_error if the internal raster does not exist (ie, you haven't created one).

		Returns:   
			void
		
		*/
		void SaveToFile(std::string outFile);


		/*
			Function: SaveToXYZ
				Saves the current in memory raster to a file in X-Y-Z format. That is:

				>x y z
				>0 0 43.5
				>0 1 41.95
				>[...]
				
				This does not modify the in memory raster.

				Currently, this does not use the xll and yll extends to calculate the correct x/y. Rather, it
				uses the position in the raster, for example, (0,4), and uses that as x/y.

			Parameters: 
				std::string outFile - File to save to
			
			Throws:
				std::runtime_error on inability to open output file
		
			Returns:   
				 
		*/
		void SaveToXYZ(std::string outFile);

		/*
			Function: ScaleBy
				Scales the current raster by a factor.
			
			Parameters: 
				double scaleFactor - Factor to scale the raster by
			
			Throws:
				std::runtime_error if there is no raster in memory
		
			Returns:   
				void 
		*/
		void ScaleBy(float scaleFactor);



		/*
			Function: GetNoData
				Returns the value used to represent 'no data'
			
			Parameters: 
				None

			Throws:
				Never
		
			Returns:   
				unsigned int - NODATA_value as defined in the raster file
		*/
		float GetNoData() const;

		/*
			Function: operator=
				 Assignment operator
			
			Parameters: 
				const AsciiRaster & rhs - Right hand side
			
			Throws:
				Never
			
			Returns:   
				Borealis::AsciiRaster& 
		*/
		AsciiRaster& operator= (const AsciiRaster& rhs);


	private:
		//Below is the standard ArcGis header in the form:
		// ncols         <int>
		// nrows         <int>
		// xllcorner     <int>
		// yllcorner     <int>
		// cellsize      <int>
		// NODATA_value  <double/int>
	
			float m_noData;

		//Helper class for ScaleBy. used as a function object for tbb::paraelle_for
		class threaded_apply_scale_by
		{
		public:
			tbb::concurrent_vector<float>* const m_raster; //pointer to raster in memory
			float scaleFactor; //scale factor to scale raster by
			unsigned int m_rowCnt; //row and row counts
			unsigned int m_colCnt;
			
			//main body. does the actual iterations
			void operator()(const tbb::blocked_range2d<unsigned int>& r) const
			{
				tbb::concurrent_vector<float>* raster = m_raster;

				for(unsigned int i=r.rows().begin(); i!=r.rows().end(); ++i )
				{
					for(unsigned int j=r.cols().begin(); j!=r.cols().end(); ++j ) 
					{
						raster->at(i*m_colCnt+j) *= scaleFactor;
					}
				}
			}
			
			threaded_apply_scale_by(float scale, unsigned int rows, unsigned int cols, tbb::concurrent_vector<float>* raster) :
			m_raster(raster)
			{
				scaleFactor = scale;

				m_rowCnt = rows;
				m_colCnt = cols;

			}

		};

		class threaded_parse
		{
			public:
				std::string* const m_lines;
				tbb::concurrent_vector<float>* const m_array;
				bool* const m_failure;
				std::string* const m_msg;
				unsigned int m_cols;

				void operator()(const tbb::blocked_range<unsigned int>& r) const;


				threaded_parse(std::string* lines,tbb::concurrent_vector<float>* array, unsigned int cols, bool* failure, std::string* msg) :
				m_lines(lines), m_array(array), m_failure(failure), m_msg(msg)
				{
					m_cols = cols;
				}

		};

	};


}


#endif